<?php
/**
 * 忘道 php简单代码混淆工具 qq：1020332177
 *
 * 目前只支持单个文件混淆，只混淆变量和protected方法，-- 功能已经符合自己需求，有兴趣的可以自己完善
 */

function usortCallback($a, $b)
{
    if (strlen($a) == strlen($b)) {
        return 0;
    }
    return (strlen($a) > strlen($b)) ? -1 : 1;
}

class confuseCode
{
    private $name_length = 30;
    private $is_bak = false;
    private $strip_whitespace = false;
    private $skip_var = array('_GET', '_POST', '_COOKIE', '_SESSION', '_FILE', '_W', '_GPC', 'this');
    private $var_array = array();
    private $method_array = array();
    private $name_array = array();

    /**
     * 开始混淆
     *
     * @param $path 混淆路径
     */
    public function start($path)
    {
        if (is_dir($path)) {
            $filepaths = $this->readAllDir($path);

            $var_array = array_filter(array_unique($this->var_array));
            $new_var = array();
            foreach ($var_array as $k => $v) {
                if (in_array($v, $this->skip_var)) {
                    continue;
                }
                $new_var[] = trim($v);
            }
            usort($new_var, 'usortCallback');
            $this->var_array = $new_var;

            $method_array = array_filter(array_unique($this->method_array));
            $new_method = array();
            foreach ($method_array as $k => $v) {
                $new_method[] = trim($v);
            }
            usort($new_method, 'usortCallback');
            $this->method_array = $new_method;

            //var_dump($this->var_array);
            //var_dump($this->method_array);
            $this->directoryMap($filepaths, $path);

        } else {
            $this->replaceFile($path);
        }
    }

    /**
     * 遍历目录替换
     *
     * @param $filepaths
     * @param string $path
     */
    private function directoryMap($filepaths, $path = '')
    {
        foreach ($filepaths as $k => $v) {
            if (is_array($v)) {
                $this->directoryMap($v, $k);
                continue;
            }
            $this->replaceFile($path . $v);
            echo $path;
            echo $v . '<br/>';
        }
    }

    /**
     * 开始替换文件
     *
     * @param $path
     */
    public function replaceFile($path)
    {
        $str = $this->strip_whitespace ? php_strip_whitespace($path) : file_get_contents($path);
        $str = $this->replaceVar($str);
        $str = $this->replaceMethod($str);
        $this->is_bak && rename($path, $path . '_bak');
        file_put_contents($path, $str);
    }

    /**
     * 混淆方法名
     *
     * @param $method_array
     * @param $str
     * @return mixed
     */
    private function replaceMethod($str)
    {
        foreach ($this->method_array as $k => $v) {
            $v = trim($v);
            $randStr = $this->randStr($v);
            $str = str_replace($v, $randStr, $str);
        }
        return $str;
    }

    /**
     * 混淆变量
     *
     * @param $var_array
     * @param $str
     * @return mixed
     */
    private function replaceVar($str)
    {
        $var_array = array_filter(array_unique($this->var_array));
        $new_var = array();
        foreach ($var_array as $k => $v) {
            if (in_array($v, $this->skip_var)) {
                continue;
            }
            $new_var[] = trim($v);
        }
        //var_dump($new_var);exit();
        usort($new_var, 'usortCallback');

        foreach ($new_var as $k => $v) {

            $randStr = $this->randStr($v);
            $str = str_replace('$' . $v . '', '$' . $randStr, $str);
        }
        return $str;
    }

    /**
     * 生成随机32位"1","l"名称
     *
     * @return string
     */
    private function randStr($vname)
    {
        if (isset($this->name_array[$vname])) {
            return $this->name_array[$vname];
        }

        $name = 'l';
        $array_str = array('1', 'l');
        for ($i = 0; $i < $this->name_length; $i++) {
            $rand = mt_rand(0, 1);
            $name .= $array_str[$rand];
        }
        if (in_array($name, $this->name_array)) {
            return $this->randStr();
        } else {
            $this->name_array[$vname] = $name;
            return $name;
        }
    }

    /**
     * 读取目录
     *
     * @param $source_dir
     * @param int $directory_depth
     * @param bool $hidden
     * @return array|bool
     */
    private function readAllDir($source_dir, $directory_depth = 0, $hidden = FALSE)
    {
        if ($fp = @opendir($source_dir)) {
            $filedata = array();
            $new_depth = $directory_depth - 1;
            $source_dir = rtrim($source_dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;

            while (FALSE !== ($file = readdir($fp))) {
                if ($file === '.' OR $file === '..' OR ($hidden === FALSE && $file[0] === '.')) {
                    continue;
                }

                is_dir($source_dir . $file) && $file .= DIRECTORY_SEPARATOR;

                if (($directory_depth < 1 OR $new_depth > 0) && is_dir($source_dir . $file)) {
                    $filedata[$source_dir . $file] = $this->readAllDir($source_dir . $file, $new_depth, $hidden);
                } else {
                    $fileextension = pathinfo($file, PATHINFO_EXTENSION);

                    if (in_array($fileextension, array('php', 'html'))) {
                        $filedata[] = $file;
                        $str = $this->strip_whitespace ? php_strip_whitespace($source_dir . $file) : file_get_contents($source_dir . $file);

                        $pattern = '#\$([a-zA-Z0-9_]*)#is';
                        preg_match_all($pattern, $str, $matches);
                        $this->var_array = array_merge($this->var_array, $matches[1]);

                        $pattern1 = '#protected|private.*function(.*)\(#iU';
                        preg_match_all($pattern1, $str, $matches1);
                        $this->method_array = array_merge($this->method_array, $matches1[1]);
                    }
                }
            }

            closedir($fp);
            return $filedata;
        }

        return FALSE;
    }
}